/*
 * Created on 22-Sep-2004
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package org.gudy.azureus2.core3.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.LogAlert;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.platform.PlatformManager;
import org.gudy.azureus2.platform.PlatformManagerCapabilities;
import org.gudy.azureus2.platform.PlatformManagerFactory;
/**
 * @author parg
 *
 */
import java.io.File;
import java.io.FileReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;

public class AEDiagnostics {
    // these can not be set true and have a usable AZ!
    public static final boolean ALWAYS_PASS_HASH_CHECKS = false;
    public static final boolean USE_DUMMY_FILE_DATA = false;
    public static final boolean CHECK_DUMMY_FILE_DATA = false;

    // these can safely be set true, things will work just slower
    public static final boolean DEBUG_MONITOR_SEM_USAGE = false;
    public static final boolean DEBUG_THREADS = true; // Leave this on by default for the moment

    public static final boolean TRACE_DIRECT_BYTE_BUFFERS = false;
    public static final boolean TRACE_DBB_POOL_USAGE = false;
    public static final boolean PRINT_DBB_POOL_USAGE = false;

    public static final boolean TRACE_TCP_TRANSPORT_STATS = false;
    public static final boolean TRACE_CONNECTION_DROPS = false;

    static {
        if (ALWAYS_PASS_HASH_CHECKS) {
            System.out.println("**** Always passing hash checks ****");
        }
        if (USE_DUMMY_FILE_DATA) {
            System.out.println("**** Using dummy file data ****");
        }
        if (CHECK_DUMMY_FILE_DATA) {
            System.out.println("**** Checking dummy file data ****");
        }
        if (DEBUG_MONITOR_SEM_USAGE) {
            System.out.println("**** AEMonitor/AESemaphore debug on ****");
        }
        if (TRACE_DIRECT_BYTE_BUFFERS) {
            System.out.println("**** DirectByteBuffer tracing on ****");
        }
        if (TRACE_DBB_POOL_USAGE) {
            System.out.println("**** DirectByteBufferPool tracing on ****");
        }
        if (PRINT_DBB_POOL_USAGE) {
            System.out.println("**** DirectByteBufferPool printing on ****");
        }
        if (TRACE_TCP_TRANSPORT_STATS) {
            System.out.println("**** TCP_TRANSPORT_STATS tracing on ****");
        }

        int maxFileSize = 256 * 1024;
        try {
            String logSize = System.getProperty("diag.logsize", null);
            if (logSize != null) {
                if (logSize.toLowerCase().endsWith("m")) {
                    maxFileSize = Integer.parseInt(logSize.substring(0, logSize.length() - 1)) * 1024 * 1024;
                } else {
                    maxFileSize = Integer.parseInt(logSize);
                }
            }
        } catch (Throwable t) {
        }
        MAX_FILE_SIZE = maxFileSize;
    }

    private static final int MAX_FILE_SIZE; // get two of these per logger type

    private static final String CONFIG_KEY = "diagnostics.tidy_close";

    private static File debug_dir;

    private static File debug_save_dir;

    private static boolean started_up;
    private static volatile boolean startup_complete;
    private static boolean enable_pending_writes;

    private static Map<String, AEDiagnosticsLogger> loggers = new HashMap<String, AEDiagnosticsLogger>();

    protected static boolean logging_enabled;
    protected static boolean loggers_enabled;

    private static List<AEDiagnosticsEvidenceGenerator> evidence_generators = new ArrayList<AEDiagnosticsEvidenceGenerator>();

    public static synchronized void startup(boolean _enable_pending) {
        if (started_up) {

            return;
        }

        started_up = true;

        enable_pending_writes = _enable_pending;

        try {
            // Minimize risk of loading to much when in transitory startup mode

            boolean transitoryStartup = System.getProperty("transitory.startup", "0").equals("1");

            if (transitoryStartup) {

                // no xxx_?.log logging for you!

                loggers_enabled = false;

                // skip tidy check and more!

                return;
            }

            debug_dir = FileUtil.getUserFile("logs");

            debug_save_dir = new File(debug_dir, "save");

            COConfigurationManager.addAndFireParameterListeners(new String[] { "Logger.Enabled", "Logger.DebugFiles.Enabled", },
                    new ParameterListener() {
                        public void parameterChanged(String parameterName) {
                            logging_enabled = COConfigurationManager.getBooleanParameter("Logger.Enabled");

                            loggers_enabled = logging_enabled && COConfigurationManager.getBooleanParameter("Logger.DebugFiles.Enabled");

                            if (!loggers_enabled) {

                                loggers_enabled =
                                        Constants.IS_CVS_VERSION || COConfigurationManager.getBooleanParameter("Logger.DebugFiles.Enabled.Force");
                            }
                        }
                    });

            boolean was_tidy = COConfigurationManager.getBooleanParameter(CONFIG_KEY);

            new AEThread2("asyncify", true) {
                public void run() {
                    SimpleTimer.addEvent("AEDiagnostics:logCleaner", SystemTime.getCurrentTime() + 60000 + RandomUtils.nextInt(15000),
                            new TimerEventPerformer() {
                                public void perform(TimerEvent event) {
                                    cleanOldLogs();
                                }
                            });
                }
            }.start();

            if (debug_dir.exists()) {

                boolean save_logs = System.getProperty("az.logging.save.debug", "true").equals("true");

                long now = SystemTime.getCurrentTime();

                File[] files = debug_dir.listFiles();

                if (files != null) {

                    boolean file_found = false;

                    for (int i = 0; i < files.length; i++) {

                        File file = files[i];

                        if (file.isDirectory()) {

                            continue;
                        }

                        if (!was_tidy) {

                            file_found = true;

                            if (save_logs) {

                                if (!debug_save_dir.exists()) {

                                    debug_save_dir.mkdir();
                                }

                                FileUtil.copyFile(file, new File(debug_save_dir, now + "_" + file.getName()));
                            }
                        }
                    }

                    if (file_found) {

                        Logger.logTextResource(new LogAlert(LogAlert.UNREPEATABLE, LogAlert.AT_WARNING, "diagnostics.log_found"),
                                new String[] { debug_save_dir.toString() });
                    }
                }
            } else {

                debug_dir.mkdir();
            }

            AEJavaManagement.initialise();

        } catch (Throwable e) {

            // with webui we don't have the file stuff so this fails with class not found

            if (!(e instanceof NoClassDefFoundError)) {

                Debug.printStackTrace(e);
            }
        } finally {

            startup_complete = true;
        }
    }

    public static void dumpThreads() {
        AEJavaManagement.dumpThreads();
    }

    /**
	 * 
	 */
    private static synchronized void cleanOldLogs() {
        try {
            long now = SystemTime.getCurrentTime();

            // clear out any really old files in the save-dir

            File[] files = debug_save_dir.listFiles();

            if (files != null) {

                for (int i = 0; i < files.length; i++) {

                    File file = files[i];

                    if (!file.isDirectory()) {

                        long last_modified = file.lastModified();

                        if (now - last_modified > 10 * 24 * 60 * 60 * 1000L) {

                            file.delete();
                        }
                    }
                }
            }

        } catch (Exception e) {
        }
    }

    public static boolean isStartupComplete() {
        return (startup_complete);
    }

    public static File getLogDir() {
        startup(false);

        return (debug_dir);
    }

    public static synchronized void flushPendingLogs() {
        for (AEDiagnosticsLogger logger : loggers.values()) {

            logger.writePending();
        }

        enable_pending_writes = false;
    }

    public static synchronized AEDiagnosticsLogger getLogger(String name) {
        AEDiagnosticsLogger logger = loggers.get(name);

        if (logger == null) {

            startup(false);

            logger = new AEDiagnosticsLogger(debug_dir, name, MAX_FILE_SIZE, !enable_pending_writes);

            loggers.put(name, logger);
        }

        return (logger);
    }

    public static void logWithStack(String logger_name, String str) {
        log(logger_name, str + ": " + Debug.getCompressedStackTrace());
    }

    public static void log(String logger_name, String str) {
        getLogger(logger_name).log(str);
    }

    public static void markDirty() {
        try {

            COConfigurationManager.setParameter(CONFIG_KEY, false);

            COConfigurationManager.save();

        } catch (Throwable e) {

            Debug.printStackTrace(e);
        }
    }

    public static boolean isDirty() {
        return (!COConfigurationManager.getBooleanParameter(CONFIG_KEY));
    }

    public static void markClean() {
        try {
            COConfigurationManager.setParameter(CONFIG_KEY, true);

            COConfigurationManager.save();

        } catch (Throwable e) {

            Debug.printStackTrace(e);
        }
    }

    private static final String[][] bad_dlls = { { "niphk", "y", }, { "nvappfilter", "y", }, { "netdog", "y", }, { "vlsp", "y", },
            { "imon", "y", }, { "sarah", "y", }, { "MxAVLsp", "y", }, { "mclsp", "y", }, { "radhslib", "y", }, { "winsflt", "y", },
            { "nl_lsp", "y", }, { "AxShlex", "y", }, { "iFW_Xfilter", "y", }, { "gapsp", "y", }, { "WSOCKHK", "n", }, { "InjHook12", "n", },
            { "FPServiceProvider", "n", }, { "SBLSP.dll", "y" }, { "nvLsp.dll", "y" }, };

    public static void checkDumpsAndNatives() {
        try {
            PlatformManager p_man = PlatformManagerFactory.getPlatformManager();

            if (p_man.getPlatformType() == PlatformManager.PT_WINDOWS && p_man.hasCapability(PlatformManagerCapabilities.TestNativeAvailability)) {

                for (int i = 0; i < bad_dlls.length; i++) {

                    String dll = bad_dlls[i][0];
                    String load = bad_dlls[i][1];

                    if (load.equalsIgnoreCase("n")) {

                        continue;
                    }

                    if (!COConfigurationManager.getBooleanParameter("platform.win32.dll_found." + dll, false)) {

                        try {
                            if (p_man.testNativeAvailability(dll + ".dll")) {

                                COConfigurationManager.setParameter("platform.win32.dll_found." + dll, true);

                                String detail = MessageText.getString("platform.win32.baddll." + dll);

                                Logger.logTextResource(new LogAlert(LogAlert.REPEATABLE, LogAlert.AT_WARNING, "platform.win32.baddll.info"),
                                        new String[] { dll + ".dll", detail });
                            }

                        } catch (Throwable e) {

                            Debug.printStackTrace(e);
                        }
                    }
                }
            }

            File app_dir = new File(SystemProperties.getApplicationPath());

            if (app_dir.canRead()) {

                File[] files = app_dir.listFiles();

                File most_recent_dump = null;
                long most_recent_time = 0;

                long now = SystemTime.getCurrentTime();

                long one_week_ago = now - 7 * 24 * 60 * 60 * 1000;

                for (int i = 0; i < files.length; i++) {

                    File f = files[i];

                    String name = f.getName();

                    if (name.startsWith("hs_err_pid")) {

                        long last_mod = f.lastModified();

                        if (last_mod > most_recent_time && last_mod > one_week_ago) {

                            most_recent_dump = f;
                            most_recent_time = last_mod;
                        }
                    }
                }

                if (most_recent_dump != null) {

                    long last_done = COConfigurationManager.getLongParameter("diagnostics.dump.lasttime", 0);

                    if (last_done < most_recent_time) {

                        COConfigurationManager.setParameter("diagnostics.dump.lasttime", most_recent_time);

                        analyseDump(most_recent_dump);
                    }
                }
            }
        } catch (Throwable e) {

            Debug.printStackTrace(e);
        }
    }

    protected static void analyseDump(File file) {
        System.out.println("Analysing " + file);

        try {
            LineNumberReader lnr = new LineNumberReader(new FileReader(file));

            try {
                boolean float_excep = false;

                String[] bad_dlls_uc = new String[bad_dlls.length];

                for (int i = 0; i < bad_dlls.length; i++) {

                    String dll = bad_dlls[i][0];

                    bad_dlls_uc[i] = (dll + ".dll").toUpperCase();
                }

                String alcohol_dll = "AxShlex";

                List<String> matches = new ArrayList<String>();

                while (true) {

                    String line = lnr.readLine();

                    if (line == null) {

                        break;
                    }

                    line = line.toUpperCase();

                    if (line.indexOf("EXCEPTION_FLT") != -1) {

                        float_excep = true;

                    } else {

                        for (int i = 0; i < bad_dlls_uc.length; i++) {

                            String b_uc = bad_dlls_uc[i];

                            if (line.indexOf(b_uc) != -1) {

                                String dll = bad_dlls[i][0];

                                if (dll.equals(alcohol_dll)) {

                                    if (float_excep) {

                                        matches.add(dll);
                                    }

                                } else {

                                    matches.add(dll);
                                }
                            }
                        }
                    }
                }

                for (int i = 0; i < matches.size(); i++) {

                    String dll = matches.get(i);

                    String detail = MessageText.getString("platform.win32.baddll." + dll);

                    Logger.logTextResource(new LogAlert(LogAlert.REPEATABLE, LogAlert.AT_WARNING, "platform.win32.baddll.info"), new String[] {
                            dll + ".dll", detail });
                }
            } finally {

                lnr.close();
            }
        } catch (Throwable e) {

            Debug.printStackTrace(e);
        }
    }

    public static void addEvidenceGenerator(AEDiagnosticsEvidenceGenerator gen) {
        synchronized (evidence_generators) {

            evidence_generators.add(gen);
        }
    }

    public static void removeEvidenceGenerator(AEDiagnosticsEvidenceGenerator gen) {
        synchronized (evidence_generators) {

            evidence_generators.remove(gen);
        }
    }

    public static void generateEvidence(PrintWriter _writer) {
        IndentWriter writer = new IndentWriter(_writer);

        synchronized (evidence_generators) {

            for (int i = 0; i < evidence_generators.size(); i++) {

                try {
                    evidence_generators.get(i).generate(writer);

                } catch (Throwable e) {

                    e.printStackTrace(_writer);
                }
            }
        }

        writer.println("Memory");

        try {
            writer.indent();

            Runtime rt = Runtime.getRuntime();

            writer.println("max=" + rt.maxMemory() + ",total=" + rt.totalMemory() + ",free=" + rt.freeMemory());

        } finally {

            writer.exdent();
        }
    }
}
